package cn.geminis.auth.engine;

import cn.geminis.auth.model.filter.UserPasswordFilter;
import cn.geminis.auth.model.repository.UserPasswordRepository;
import cn.geminis.resources.client.UserClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * @author puddi
 */
@RequiredArgsConstructor
@Component
@Slf4j
public class PasswordAuthenticationProvider implements AuthenticationProvider {

    private static final String ERROR_MSG = "用户名或密码不正确";

    private final UserPasswordRepository repository;
    private final PasswordEncoder passwordEncoder;
    private final UserClient userClient;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        var username = this.getUsername(authentication);
        var password = (String) authentication.getCredentials();
        var userId = checkPassword(authentication);
        var roles = getAuthorities(userId);

        return new UsernamePasswordAuthenticationToken(
                new User(username + "/" + userId, password, roles),
                password,
                roles
        );
    }

    private String getUsername(Authentication authentication) {
        var username = authentication.getName();
        if (!StringUtils.hasText(username)) {
            if (log.isWarnEnabled()) {
                log.warn("用户尝试登录，但用户名为空");
            }
            throw new UsernameNotFoundException(ERROR_MSG);
        }
        return username;
    }

    private String checkPassword(Authentication authentication) {
        var username = authentication.getName();
        var password = (String) authentication.getCredentials();
        var userPassword = repository
                .findOne(
                        UserPasswordFilter.builder()
                                .username(username)
                                .build()
                )
                .orElseThrow(() -> {
                    if (log.isWarnEnabled()) {
                        log.warn("用户尝试登录，但用户不存在，用户：{}", username);
                    }
                    return new UsernameNotFoundException(ERROR_MSG);
                });

        if (!passwordEncoder.matches(password, userPassword.getPassword())) {
            if (log.isDebugEnabled()) {
                log.debug("用户密码认证失败，用户：{}", username);
            }
            throw new BadCredentialsException(ERROR_MSG);
        }
        return userPassword.getId();
    }

    private List<? extends GrantedAuthority> getAuthorities(String userId) {
        var roleNames = userClient.userRoleName(userId);
        if (log.isDebugEnabled()) {
            log.debug("获取用户信息，用户:{}, 角色：{}", userId, String.join(",", roleNames.toArray(new String[0])));
        }
        return roleNames.stream()
                .map(role -> (GrantedAuthority) () -> role)
                .toList();
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
